﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Content;
using CellarDoor.Gui.Animation;
using CellarDoor.Gui.Theming;
using CellarDoor.Gui.Controller;
using CellarDoor.Gui.Layout;

namespace CellarDoor.Gui.Elements
{
    public partial class UiElement
    {
        public UiElement()
        {
            this.elements = new UiElementCollection(this);
            this.elements.ControlAdded += elements_ControlAdded;
            this.elements.ControlRemoved += elements_ControlRemoved;

            this.NavigateItem = new UiNavigateNode(this);
            this.Layout = new UiLayout(this);
            this.Style = new UiVisualStyle(this);

            Clickable = true;
            Color = Vector3.One;
            Opacity = 1;
            TextColor = Vector3.One;
            Enabled = true;
            Visible = true;
        }

        public UiElement(UiElement parent)
            : this()
        {
            if (parent == null)
            {
                throw new ArgumentNullException("parent");
            }

            parent.AddElement(this);

            // inherit the ui manager
            if (parent.manager != null)
            {
                this.Manager = parent.manager;
            }
        }

        /// <summary>
        /// Sets the manager to be used
        /// </summary>
        /// <param name="manager"></param>
        internal void SetManager(UiManager manager)
        {
            if (manager == null)
            {
                throw new ArgumentNullException("manager");
            }
            if (parent != null)
            {
                throw new InvalidOperationException(
                    "Cannot set manager on a child element. Change manager is allowed only on root elements.");
            }

            this.Manager = manager;
        }

        void OnManagerViewportChanged(object sender, EventArgs e)
        {
            InvalidateLayout();
        }

        void elements_ControlRemoved(object sender, UiElementEventArgs e)
        {
            InvalidateLayout();
        }

        void elements_ControlAdded(object sender, UiElementEventArgs e)
        {
            InvalidateLayout();
        }

        public void Invalidate()
        {
            InvalidateLayout();
            InvalidateStyle();
            InvalidateTheme();
        }

        public void InvalidateLayout()
        {
            Layout.Invalid = true;
        }

        public void InvalidateTheme()
        {
            Style.ThemeInvalid = true;
        }

        public void InvalidateStyle()
        {
            Style.StateInvalid = true;
        }

        /// <summary>
        /// Gets a child element that is in target of the given point
        /// </summary>
        /// <param name="point">The casting point</param>
        /// <param name="native">Value indicating whether the point is in native resolution or not.</param>
        /// <returns></returns>
        public UiElement GetElementFromScreenPoint(Point point, bool native)
        {
            return GetElementFromScreenPoint(point, native, false);
        }

        /// <summary>
        /// Gets a child element that is in target of the given point
        /// </summary>
        /// <param name="point">The casting point</param>
        /// <param name="native">Value indicating whether the point is in native resolution or not.</param>
        /// <returns></returns>
        public UiElement GetElementFromScreenPoint(Point point, bool native, bool includeInvisible)
        {
            if (!Visible && !includeInvisible)
            {
                return null;
            }

            UiElement result = null;
            for (int i = 0; i < Elements.Count; i++)
            {
                UiElement temp = Elements[i].GetElementFromScreenPoint(point, native);
                if (temp != null)
                {
                    result = temp;
                }
            }
            if (result == null)
            {
                if ((native && Layout.Area.Contains(point)) || (!native && Layout.AreaScaled.Contains(point)))
                {
                    return this;
                }
            }

            return result;
        }

        /// <summary>
        /// Adds a child element
        /// </summary>
        /// <param name="element"></param>
        /// <returns></returns>
        public virtual UiElement AddElement(UiElement element)
        {
            elements.Add(element);
            return element;
        }

        protected virtual void OnStateChanged()
        {
            Style.StateInvalid = true;
            if (StateChanged != null)
            {
                StateChanged(this, new UiEventArgs(this));
            }
        }

        public virtual void Update(GameTime gameTime)
        {
            if (!Enabled)
            {
                return;
            }

            controllers.Update((float)gameTime.ElapsedGameTime.TotalMilliseconds);

            if (Style.ThemeInvalid)
            {
                Style.Update();
                Style.ThemeInvalid = false;
            }
            bool childrenInvalid = Layout.Invalid;
            if (Layout.Invalid)
            {
                Layout.Update();
                Style.SetTargetArea(Layout.AreaScaled);
                Layout.Invalid = false;

                OnBoundsChanged();
            }
            if (Style.StateInvalid)
            {
                Style.Update();
                Style.StateInvalid = false;
            }

            foreach (UiElement c in elements)
            {
                if (childrenInvalid)
                {
                    c.Layout.Invalid = childrenInvalid;
                }
                
                c.Update(gameTime);
            }
        }

        /// <summary>
        /// Draws the whole element and chidren
        /// </summary>
        /// <param name="color"></param>
        public virtual void Draw(Vector4 color)
        {
            if (!Visible)
            {
                return;
            }

            // accumulate color and opacity
            color *= new Vector4(this.Color * this.Opacity, this.Opacity);
            
            if (ScissorEnable)
            {
                Manager.BeginScissor(Scissor);
            }

            DrawElement(color);
            DrawText(color);
            DrawChildren(color);

            if (ScissorEnable)
            {
                Manager.EndScissor();
            }
        }

        /// <summary>
        /// Draws this element without children using the given color as tint
        /// </summary>
        /// <param name="color"></param>
        protected virtual void DrawElement(Vector4 color)
        {
            Style.Draw(Manager, new Color(color));
        }

        /// <summary>
        /// Draws the text of this element with given coloe
        /// </summary>
        /// <param name="color"></param>
        protected virtual void DrawText(Vector4 color)
        {
            if (Style.Font != null && text != null)
            {
                color *= new Vector4(TextColor, 1);
                Manager.DrawString(Style.Font, text, Layout.TextPositionScaled, new Color(color), 1, 0);
            }
        }

        /// <summary>
        /// Calls Draw on each child with given color
        /// </summary>
        /// <param name="color"></param>
        protected virtual void DrawChildren(Vector4 color)
        {
            for (int i = elements.Count - 1; i >= 0; i--)
            {
                elements[i].Draw(color);
            }
        }

        /// <summary>
        /// Clears the child collection
        /// </summary>
        public virtual void Clear()
        {
            Elements.Clear();
        }

        /// <summary>
        /// If the element has a parent, then it is removed from its collection and is no longer a child
        /// </summary>
        public void Detach()
        {
            if (Parent != null)
            {
                Parent.Elements.Remove(this);
            }
        }
    }
}
